#include "Config.h"


map<string,string> Config::m_mapConfigParam;

string& trim_right(string& s,  const std::string& delimiters)
{
    return s.erase( s.find_last_not_of( delimiters ) + 1 );
}

string& trim_left(string & s, const string& delimiters)
{
    return s.erase( 0,  s.find_first_not_of( delimiters ) );
}

std::string& trim(string& s,  const std::string& delimiters)
{
    return trim_left( trim_right( s,  delimiters ),  delimiters);
}

enum eValueType
{
    eINT, 
    eSTRING, 
    eFILEPATH, 
    eIP, 
    eBOOL, 
};

typedef struct
{
    string name;
    char eValueType;
    string strdefault;
    int nLowerBound;
    int nUppderBound;
}validationRule;


//for type eINT default, lb and ub must be provided and default should be in between lb and ub.
// for type eIP ip should be valid ipv4 address.lb and ub not applicable.
// for eFILEPATH file path should be be present. lb and ub not applicable.
// for eBOOL value should be either true or false. lb and ub not applicable.

validationRule validationRuleTable[] =
{
    // name                         type    default     lb      ub
    { "CServerId",                   eINT,  "1",           0,       1000}, 
    { "CServerIPAddress",            eIP,   "127.0.0.1"                 },
    {"CInterfaceIPAddress",          eIP,   "127.0.0.1"                 }, 
    {"CServerCInterfacePort",        eINT,  "1201",     1000, 50000      }, 
    {"CInterfaceCServerPort",        eINT,  "1202",     1000, 50000      }, 
    {"MDBPort",                      eINT,  "8888",     1000, 50000      }, 
    {"MDBPortBroadcast",             eINT , "8889",     1000, 50000      }, 
    {"MDBIPAddress",                 eIP,   "127.0.0.1"                 }, 
    {"CDBDevicePort",                eINT , "1212",      1000, 50000      }, 
    {"CDBIPAddress",                 eIP,    "127.0.0.1",                }, 
    {"CDBReconnectionTime",          eINT,  "5",         1, 20           }, 
    {"AchintyaFileDir",              eSTRING, "Dir",    }, 
    {"AchintyaFilePersistance_1",    eBOOL,    "true",  }, 
    {"LogFile",                      eFILEPATH, "Log",  }, 
    {"LogLevel",                     eINT,      "14",   0 , 20 }, 
    {"LogStdoutLevel",               eINT,      "14",   0 , 20 }, 
    {"PreritFileSizeMB",             eINT,      "80" ,  1,1024 }, 
    {"NumSocketData",                eINT,      "200",  1, 1024}, 
};

bool GetRule(const string& param, validationRule& rule)
{
    bool bStatus = false;

    int iElemcount = sizeof(validationRuleTable)/sizeof(validationRule);
    for (int i = 0; i< iElemcount;++i)
    {
        if (validationRuleTable[i].name == param)
        {
            rule = validationRuleTable[i];
            bStatus = true;
            break;
        }
    }
    return bStatus;
}
bool IsValidIP(string& ip)
{    
    //works only for ipv4
#if 0
    int iRet = inet_addr(ip.c_str());
    if (INADDR_NONE != iRet )
    {
        return true;
    }
#else
    stringstream ss(ip);
    char ch;
    int a,b,c,d;
    ss >> a >> ch >> b >> ch >> c >> ch >> d;
    if (((a>=0) && (a<=0xFF)) &&
        ((b>=0) && (b<=0xFF)) &&
        ((c>=0) && (c <=0xFF)) &&
        ((d>=0) && (d <=0xFF))
        )
    {
        return true;
    }    
#endif
    cout << "Value "  <<ip << " is not a valid IPv4 address."<<endl;
    return false;
}
bool TryOpen(string& path)
{
    ifstream f;
    f.open(path);

    if (f.is_open())
    {
        f.close();
        return true;
    }
    return false;
}

bool GetValidPath (string& path)
{
    bool bStat = false;
    do
    {
        bStat = TryOpen(path);
        if ( true == bStat)
        {
            break;
        }
        // file path is not valid
        //issue with escapse characters?
#if defined _WIN32 || defined _WIN64
        size_t found = 0,  next = 0;
        string temp = path;
        while( (found = temp.find('\\', next)) != std::string::npos )
        {
            temp.insert(found, "\\");
            next = found + 4;
        }
        bStat = TryOpen(temp) ;
        if ( true == bStat)
        {
            path = temp;
            break;
        }        
#endif

    } while(0);
    return bStat;

}

Config::Config(const string &path)
{
    ifstream infile;
    do
    {

        //initialize default config param and value
        int iElemcount = sizeof(validationRuleTable)/sizeof(validationRule);
        for (int i = 0; i< iElemcount;++i)
        {
            m_mapConfigParam.insert ( std::pair<string, string>(validationRuleTable[i].name, validationRuleTable[i].strdefault) );
        }
        //now override default value with user defined value
        infile.open(path.c_str(), std::ifstream::in);
        if(infile.fail ())
        {
            cout << "Config file  " << path << " can not be opened.";
            return;
        }
        string line;
        while ( getline (infile, line) )
        {
            //cout << line << endl;
            if (true == ProcessLine(line))
            {
                //cout << line << endl;
            }
            else
            {
                cout <<"Unexpected format exiting";
                break;
            }
        }

    } while(0);
    infile.close();
}


bool Config::ProcessLine(string line)
{
    bool bStat = true;
    do
    {
        if (line.size() <3)
        {
            break;
        }
        string delimiters = " \f\n\r\t\v";
        line = trim(line, delimiters) ;

        if (( '#' == line.at(0)) ||
            '/' == line.at(0) &&
            '/' == line.at(1)
            )
        {
            // found commented line
        }
        else
        {
            //looks things valid

            string name = "", value = "";
            std::size_t found = line.find_first_of("=");
            if ( std::string::npos != found) //does not consider line with muliple = :)
            {
                name = line.substr(0, found);
                value = line.substr(found + 1);

                name = trim(name, delimiters);
                value = trim (value, delimiters);

                //cout << "name = " << name << " and value = " << value <<endl;
                //cout << "name = " << name <<endl;
                ValidateAndInsert(name,value);
            }
            else
            {
                cout << "Following line is not formatted properly =>" <<endl << line <<endl;
                bStat = false;
                break;
                //line not formatted properly
            }
        }
    }
    while(0);
    return bStat;
}

bool Config::ValidateAndInsert(string & param,string & value)
{
    validationRule rule;

    bool bStat = GetRule(param, rule);
    if (false == bStat)
    {
        return false;
    }
    bStat =  false;
    switch(rule.eValueType)
    {
        
    case eINT:
        {
            int iVal = atoi(value.c_str());
            if((iVal>rule.nLowerBound) && (iVal < rule.nUppderBound)) // value is in range
            {                
                bStat = true;
            }
        }
        break;
    case eIP:
        {
            bStat = IsValidIP(value);
        }
        break;
    case eSTRING:
        {
            bStat = true;
        }
        break;
    case eFILEPATH:
        {            
            bStat = GetValidPath(value);
        }
        break;
    case eBOOL:
        {
            if (("true" == value) || ("false" == value))
            {
                bStat =  true;
            }           
        }
        break;
    default:
        break;

    }
    if (true == bStat )
    {   
        //replace value with config file value
        m_mapConfigParam[param] = value;
    }
    else
    {
        cout << "Error in parameter: " <<param << " value found in config file : " <<value <<endl
                    <<"Ignoring config file value and applying default value : " << rule.strdefault <<endl;
        
    }
    return true;
}


bool Config::GetValue(const string& param, int& iVal)
{
    bool bStat = false;
    do
    {
        std::map<string , string >::iterator it = m_mapConfigParam.find(param);
        if (it == m_mapConfigParam.end())
        {
            break;
        }
        string value = it->second;
        iVal = atoi(value.c_str()); 
        bStat = true;
    } while(0);
    return bStat;
}
bool Config::GetValue(const string& param, string& strVal)
{
    bool bStat = false;
    do
    {
        std::map<string , string >::iterator it = m_mapConfigParam.find(param);

        if (it == m_mapConfigParam.end())
        {
            break;
        }
        strVal = it->second;
        bStat = true;
    }while(0);
    return bStat;
}

bool Config::GetValue(const string& param,  bool& bVal)
{
    bool bStat = false;
    do
    {
        std::map<string , string >::iterator it = m_mapConfigParam.find(param);
        if (it == m_mapConfigParam.end())
        {
            break;
        }
        // param found
        string strVal = it->second;
        if ("true" == strVal)
        {
            bVal = true;
        }
        else if ("false" == strVal)
        {
            bVal = false;
        }        
        bStat = true;
    } while(0);
    return bStat;
}

Config::~Config(void)
{
    m_mapConfigParam.clear();
}
